package xyz.heyaoshare.config.satoken;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jboss.logging.MDC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import xyz.heyaoshare.common.entity.bo.Result;
import xyz.heyaoshare.config.properties.WebProperties;
import xyz.heyaoshare.config.satoken.context.UserContextHolder;
import xyz.heyaoshare.core.entity.system.User;
import xyz.heyaoshare.core.enums.system.user.UserStatusEnum;
import xyz.heyaoshare.core.mapper.system.UserMapper;
import xyz.heyaoshare.exceptions.code.ErrorCode;
import xyz.heyaoshare.exceptions.customize.ServerException;
import xyz.heyaoshare.utils.JSONUtils;
import xyz.heyaoshare.utils.StrUtils;

import java.util.List;
import java.util.Objects;

import static xyz.heyaoshare.exceptions.code.ErrorInfo.*;
import static xyz.heyaoshare.exceptions.customize.ServerException.exception;


/**
 * SaToken配置类
 * @author YueHe
 * @version 0.0.1
 * @since 2025/6/25 上午11:45
 */
@Slf4j
@Configuration
public class SaTokenConfig {

    @Resource
    private WebProperties webProperties;
    @Resource
    private UserMapper userMapper;

    /**
     * Sa-Token 整合 jwt (Simple 简单模式)
     * <p>
     * 详情参照：<a href="https://sa-token.cc/doc.html#/plugin/jwt-extend">使用方法</a>
     */
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForSimple();
    }

    /**
     * 注册 [Sa-Token全局过滤器]
     */
    @Bean
    public SaServletFilter saServletFilter() {
        List<String> whiteList = webProperties.getWhiteList();
        return new SaServletFilter().addInclude("/**")// 指定 拦截路由
                .setExcludeList(whiteList)// 指定 放行路由
                // 认证函数: 每次请求执行
                .setAuth(obj -> {
                    // 登录认证 -- 拦截所有路由，并排除/login用于开放登录 以及排除设置的路由
                    for (String string : whiteList) {
                        SaRouter.match("/**", string, StpUtil::checkLogin);
                    }
                    // 如果已经登陆，则不用在登陆了
                    if (StpUtil.isLogin()) {
                        long userId = StpUtil.getLoginIdAsLong();
                        User user = userMapper.selectOneById(userId);
                        if (user != null) {
                            if (user.getStatus() == UserStatusEnum.DISABLE.getValue()) {
                                log.error("用户【{}】已被禁用，请联系管理员", user.getNickname());
                                String tokenValue = StpUtil.getTokenValue();
                                StpUtil.logoutByTokenValue(tokenValue);
                                log.warn("用户【{}】已被强制退出!", user.getNickname());
                                throw exception(USER_IS_DISABLED);
                            }
                        }
                        // 设置上下文
                        UserContextHolder.set(user);
                        // 设置MDC
                        MDC.put("userId", userId);
                    }
                })
                // 异常处理函数：每次认证函数发生异常时执行此函数
                .setError(e -> {
                    log.error("认证失败：{}", e.getMessage());
                    // 设置响应头
                    SaResponse response = SaHolder.getResponse();
                    response.setHeader("Content-Type", "application/json;charset=UTF-8");

                    // 处理异常
                    return switch (e) {
                        // 登录认证失败
                        case NotLoginException ignored -> {
                            log.error("未能通过登录认证校验...");
                            ErrorCode unauthorized = UNAUTHORIZED;
                            if (Objects.equals(ignored.getType(), NotLoginException.BE_REPLACED)) {
                                String tokenValue = StpUtil.getTokenValue();
                                if (StrUtils.isNotBlank(tokenValue)) {
                                    log.error("你的账号已在其他地方登录，请重新登录");
                                    StpUtil.logoutByTokenValue(tokenValue);
                                    unauthorized.setMessage("你的账号已在其他地方登录，请重新登录");
                                }
                            }
                            response.setStatus(unauthorized.getCode());
                            yield JSONUtils.toJsonStr(Result.error(unauthorized));
                        }
                        // 权限认证失败
                        case NotPermissionException ignored -> {
                            log.error("未能通过权限认证校验...");
                            response.setStatus(FORBIDDEN.getCode());
                            yield JSONUtils.toJsonStr(Result.error(FORBIDDEN));
                        }
                        // 角色认证失败
                        case NotRoleException ignored -> {
                            log.error("未能通过角色认证校验...");
                            response.setStatus(FORBIDDEN.getCode());
                            yield JSONUtils.toJsonStr(Result.error(FORBIDDEN));
                        }
                        // 自定义异常处理
                        case ServerException ignored -> {
                            log.error("自定义异常：{}", ignored.getMessage());
                            response.setStatus(ignored.getCode() == 401 ? 401 : 200);
                            yield JSONUtils.toJsonStr(Result.error(ignored.getCode(), ignored.getMessage()));
                        }
                        // 其他异常
                        default -> {
                            log.error("其他异常...", e);
                            response.setStatus(SERVER_ERROR.getCode());
                            yield JSONUtils.toJsonStr(Result.error(e.getMessage()));
                        }
                    };
                })
                // 前置函数：在每次认证函数之前执行（BeforeAuth 不受 includeList 与 excludeList 的限制，所有请求都会进入）
                .setBeforeAuth(r -> SaHolder.getResponse().setServer("spring-boot-cli").setHeader("X-Frame-Options", "SAMEORIGIN").setHeader("X-XSS-Protection", "1; mode=block").setHeader("X-Content-Type-Options", "nosniff"));
    }

}
